// =======================================================================
// 
//       Filename:  parser.cpp
// 
//    Description:  
// 
//        Version:  1.0
//        Created:  26.03.2010 19:52:09
//       Revision:  none
//       Compiler:  g++
// 
//         Author:  Dominik 'domachine' Burgdörfer (-), dominik.burgdoerfer@googlemail.com
//        Company:  -
// 
// =======================================================================

#include "parser.hpp"
#include "../utils/citerator.hpp"
#include "../utils/regexitermatch.hpp"
#include "../exceptions/syntaxerror.hpp"
#include "commandelement.hpp"
#include "textelement.hpp"
#include "scriptelement.hpp"
#include "varelement.hpp"
#include "file.hpp"
#include "../types/float.hpp"
#include "../types/integer.hpp"
#include <boost/regex.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <fstream>

using namespace std;
using namespace boost;
using webplodder::exceptions::SyntaxError;

namespace webplodder {
    namespace templatefile {
        Parser::Parser()
            : mLine(0)
        {

        }

        void Parser::parse(File& templateFile)
        {
            ifstream input(templateFile.sourcePath().string().c_str());
            mIterator.setStream(input);
            mLine = 1;
            bool textBlock = false;
            TextElement* textElement = 0;

            for(; mIterator.good(); mIterator.moveForward()) {
                if(mIterator.currentChar() != '{') {
                    if(!textBlock) {
                        textElement = new TextElement(
                                templateFile,
                                mIterator.position(), -1);

                        textBlock = true;
                    }

                    if(mCacheMode == CACHE_ALL)
                        (*textElement) << mIterator.currentChar();
                    if(mIterator.currentChar() == '\n')
                        ++mLine;
                }
                else {
                    if(mIterator.nextChar() == '{') {
                        if(mCacheMode == CACHE_ALL)
                            (*textElement) << mIterator.currentChar();
                        safelyIncr();
                        continue;
                    }

                    if(textBlock) {
                        textElement->setEnd(mIterator.position());
                        templateFile.push_back(
                                shared_ptr<TextElement>(
                                    textElement));
                        textBlock = false;
                    }

                    templateFile.push_back(parseCmd(templateFile));
                }
            }

            if(textElement->end() == -1) {
                textElement->setEnd(mIterator.position());
                templateFile.push_back(
                        shared_ptr<TextElement>(
                            textElement));
            }
        }

        utils::CIterator* Parser::iterator()
        {
            return &mIterator;
        }

        void Parser::setCacheMode(CacheMode mode)
        {
            mCacheMode = mode;
        }

        void Parser::safelyIncr()
        {
            mIterator.moveForward();

            if(!mIterator.good())
                throw SyntaxError("unexpected end-of-stream.",
                        mLine);
        }

        void Parser::skipWhiteSpace()
        {
            regex spaceregex("[ \t\n]");

            while(utils::regexIterMatch(mIterator, spaceregex)) {
                if(mIterator.currentChar() == '\n')
                    ++mLine;

                safelyIncr();
            }
        }

        shared_ptr<ScriptElement> Parser::parseScript(
                File& templateFile)

        {
            regex scriptregex("[a-z0-9\\-_A-Z]");
            string scriptSpec;
            shared_ptr<ScriptElement> scriptElement( 
                    new ScriptElement(templateFile));

            safelyIncr();

            skipWhiteSpace();

            while(utils::regexIterMatch(mIterator, scriptregex)) {
                scriptSpec.push_back(mIterator.currentChar());
                safelyIncr();
            }

            if(scriptSpec.size() == 0)
                throw SyntaxError("no script specification",
                        mLine);

            scriptElement->setLine(mLine);

            skipWhiteSpace();

            scriptElement->setBegin(mIterator.position());

            while(true) {
                if(mIterator.currentChar() == '!' && 
                        mIterator.nextChar() == '}') {
                    safelyIncr();
                    scriptElement->setEnd(mIterator.position());
                    break;
                }
                else {
                    char cChar = mIterator.currentChar();
                    if(mCacheMode == CACHE_ALL || mCacheMode == CACHE_SCRIPT)
                        (*scriptElement) << cChar;
                    if(cChar == '\n')
                        mLine++;
                    safelyIncr();
                }
            }

            scriptElement->setSpecification(scriptSpec);

            return scriptElement;
        }

        shared_ptr<VarElement> Parser::parseVar(File& templateFile)
        {
            string cmdName;
            regex cmdregex("[a-zA-Z_0-9]");

            safelyIncr();
            skipWhiteSpace();
            while(utils::regexIterMatch(mIterator, cmdregex)) {
                cmdName.push_back(mIterator.currentChar());
                safelyIncr();
            }

            shared_ptr<VarElement> varElement(
                    new VarElement(templateFile));
            varElement->setName(cmdName);
            varElement->setLine(mLine);

            skipWhiteSpace();

            if(mIterator.currentChar() != '}')
                throw SyntaxError(
                        "unexpected char, expected '}'",
                        mLine);
            return varElement;
        }

        shared_ptr<Element> 
            Parser::parseCmd(File& templateFile) 
        {
            string cmdName;
            int cmdLine;
            bool braceOpen = false;
            regex cmdregex("[a-zA-Z_0-9]");

            braceOpen = true;
            safelyIncr();

            skipWhiteSpace();

            if(mIterator.currentChar() == '!') {
                return parseScript(templateFile);
            }
            else if(mIterator.currentChar() == '$') {
                return parseVar(templateFile);
            }

            while(utils::regexIterMatch(mIterator, cmdregex)) {
                cmdName.push_back(mIterator.currentChar());
                safelyIncr();
            }

            if(cmdName.size() == 0)
                throw SyntaxError("no or no valid command name",
                        mLine);

            cmdLine = mLine;

            skipWhiteSpace();

            vector<shared_ptr<types::Object> > args;

            if(mIterator.currentChar() == ':') {
                safelyIncr();

                skipWhiteSpace();

                string currentArg;
                while(true) {
                    if(mIterator.currentChar() == '"' ||
                            mIterator.currentChar() == '\'') {
                        args.push_back(parseString());
                        safelyIncr();
                        skipWhiteSpace();
                    }
                    else if(mIterator.currentChar() == '{') {
                        args.push_back(parseCmd(
                                    templateFile));

                        safelyIncr();
                        skipWhiteSpace();
                    }
                    else if(mIterator.currentChar() == '}')
                        break;
                    else if(mIterator.currentChar() == ',') {
                        safelyIncr();
                        skipWhiteSpace();
                    }
                    else {
                        args.push_back(parseNumber());
                        skipWhiteSpace();
                    }
                }
            }
            else if(mIterator.currentChar() != '}') {
                throw SyntaxError(
                        "unexpected char, expected '}' or ':'",
                        mLine);
            }

            if(cmdName == "set" && mIterator.nextChar() == '\n') {
                safelyIncr();
                mLine++;
            }

            shared_ptr<CommandElement> element(
                    new CommandElement(templateFile, 
                        cmdName, args));

            element->setLine(cmdLine);
            return element;
        }

        shared_ptr<types::String> Parser::parseString() {
            char delimiter = '"';
            shared_ptr<types::String>
                buffer(new types::String(""));

            if(mIterator.currentChar() == '\'')
                delimiter = '\'';

            char charBeforeLast;

            do {
                char currentChar;
                charBeforeLast = mIterator.lastChar();
                safelyIncr();

                if(mIterator.currentChar() == '\\')
                    if(mIterator.lastChar() != '\\')
                        continue;

                if(mIterator.currentChar() == delimiter) {
                    if(mIterator.lastChar() != '\\' || charBeforeLast == '\\')
                        break;
                }
                //else if(mIterator.currentChar() == 't') {
                //    if(mIterator.lastChar() == '\\' && charBeforeLast != '\\')
                //        currentChar = '\t';
                //}
                //else if(mIterator.currentChar() == 'n') {
                //    if(mIterator.lastChar() == '\\' && charBeforeLast != '\\')
                //        currentChar = '\n';
                //}
                if(mIterator.lastChar() == '\\' && charBeforeLast != '\\') {
                    if(mIterator.currentChar() == 'n')
                        currentChar = '\n';
                    else if(mIterator.currentChar() == 't')
                        currentChar = '\t';
                    else {
                        string errMsg("invalid escape sequence: \\");
                        errMsg.push_back(mIterator.currentChar());
                        throw SyntaxError(errMsg,
                                          mLine);
                    }
                }
                else
                    currentChar = mIterator.currentChar();

                buffer->push_back(currentChar);

                if(mIterator.currentChar() == '\n') {
                    if(mIterator.lastChar() == '\\' &&
                            charBeforeLast != '\\')
                        buffer->erase(buffer->end()-1);
                    ++mLine;
                }
            } while(true);

            return buffer;
        }

        shared_ptr<types::Object> Parser::parseNumber() {
            regex numRegex("[0-9\\.\\-]");
            string buffer;

            while(utils::regexIterMatch(mIterator, numRegex)) {
                buffer.push_back(mIterator.currentChar());
                safelyIncr();
            }

            if(buffer.empty())
                throw SyntaxError("unexpected token", 
                        mLine);
            else {
                regex floatRegex("\\-?[0-9]+\\.[0-9]+");
                regex intRegex("\\-?[0-9]+");

                if(regex_match(buffer, floatRegex)) {
                    double f = atof(buffer.c_str());

                    if(f == HUGE_VAL || f == -HUGE_VAL)
                        throw SyntaxError("double value out of range",
                                mLine);

                    shared_ptr<types::Float> floatObj(
                            new types::Float(f));

                    return floatObj;
                }
                else if(regex_match(buffer, intRegex)) {
                    int i = atoi(buffer.c_str());

                    if(i == INT_MAX || i == INT_MIN)
                        throw SyntaxError("integer value out of range",
                                mLine);

                    shared_ptr<types::Integer> 
                        integer(new types::Integer(i));
                    return integer;
                }
                else 
                    throw SyntaxError("unexpected token", 
                            mLine);
            }
        }
    }
}
